#include <basic/errcode.h>
#include <basic/datetime.h>
#include <basic/sock.h>

enum _wait_option
{/*{{{*/
    _RECV=0,
    _SEND,
    _ACCEPT
};/*}}}*/

#define _MSG_IS_WRAP (flag & WRAP)

static int _wait(unsigned char type, SOCKET sock, int *o_timeout_ms)
{/*{{{*/
    int timeout_ms=*o_timeout_ms;
    fd_set rset, wset;
    struct timeval tv;
    int res=0;
#ifdef __linux__
#else
    int64_t begin;
    int used;
#endif

    if(timeout_ms < 0)
        return(0);

    switch(type)
    {
        case _RECV:
        case _ACCEPT:
            FD_ZERO(&rset);
            FD_SET(sock, &rset);
            break;
        case _SEND:
            FD_ZERO(&wset);
            FD_SET(sock, &wset);
            break;
        default:
            return(-1);
    }

AGAIN:
    tv.tv_sec=timeout_ms/1000;
    tv.tv_usec=timeout_ms%1000*1000;
#ifdef __linux__
#else
    gettimeofday(&tv, NULL);
    begin=tv.tv_sec*1000LL+tv.tv_usec/1000;
#endif
    switch(type)
    {
        case _RECV:
        case _ACCEPT:
            res=select(sock+1, &rset, NULL, NULL, &tv);
            break;
        case _SEND:
            res=select(sock+1, NULL, &wset, NULL, &tv);
            break;
    }
    if(res == SOCKET_ERROR)
    {
        if(get_last_error(NULL) == EINTR)
            goto AGAIN;
        return(-1);
    }
    if(res == 0)
    {
        return(RET_ERR_TIMEOUT);
    }
    //printf("sec:%ld usec:%ld\n", tv.tv_sec, tv.tv_usec);

#ifdef __linux__
    *o_timeout_ms=tv.tv_sec*1000+tv.tv_usec/1000;
#else
    gettimeofday(&tv, NULL);
    used=tv.tv_sec*1000LL+tv.tv_usec/1000-begin;
    *o_timeout_ms=timeout_ms > used ? timeout_ms-used : 0;
#endif
    return(0);
}/*}}}*/

/*==============*/
/* 传统阻塞方式 */
/*==============*/
SOCKET sock_listen(int server_port)
{/*{{{*/
    SOCKET listen_sock;
    int optval;
    struct sockaddr_in server_addr;

    if((listen_sock=socket(AF_INET, SOCK_STREAM, 0)) ==
			INVALID_SOCKET)
		return(INVALID_SOCKET);
    optval=1;
    if(setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR,
           &optval, sizeof(optval)) == SOCKET_ERROR)
        return(INVALID_SOCKET);
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family=AF_INET;
    server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    server_addr.sin_port=htons(server_port);
    if(bind(listen_sock, (const struct sockaddr *)&server_addr,
				sizeof(server_addr)) == SOCKET_ERROR)
        return(INVALID_SOCKET);
    if(listen(listen_sock, 1024) == SOCKET_ERROR)
    {
        return(INVALID_SOCKET);
    }

    return(listen_sock);
}/*}}}*/

SOCKET sock_connect(char *server, int server_port, int timeout)
{/*{{{*/
    char server_ip[IP_SIZE+1];
    int res;
    SOCKET sock;
    int flags;
    struct sockaddr_in client_addr, server_addr;
    fd_set rset, wset;
    struct timeval tv;

    if(host_name_to_ip(server?server:"localhost", server_ip) == NULL)
        return(INVALID_SOCKET);

    if((sock=socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
        return(INVALID_SOCKET);
    memset(&client_addr, 0, sizeof(client_addr));
    client_addr.sin_family=AF_INET;
    client_addr.sin_port=htons(server_port);
    if(inet_pton(AF_INET, server_ip, &client_addr.sin_addr)<=0)
        goto ERROR_OUT;
    if((flags=fcntl(sock, F_GETFL, 0)) == -1 ||
            fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1)
        goto ERROR_OUT;
    if((res=connect(sock, (const struct sockaddr *)&client_addr,
                    sizeof(client_addr))) == SOCKET_ERROR)
    {
        if(get_last_error(NULL) != EINPROGRESS)
            goto ERROR_OUT;
    }
    else
        goto DONE;

    FD_ZERO(&rset);
    FD_SET(sock, &rset);
    wset=rset;
AGAIN:
    tv.tv_sec=timeout;
    tv.tv_usec=0;
    if((res=select(sock+1, &rset, &wset, NULL,
                    (timeout>=0)?&tv:NULL)) == SOCKET_ERROR)
    {
        int errnum=get_last_error(NULL);
        if(errnum == EINTR)  goto AGAIN;
        goto ERROR_OUT;
    }
    if(res == 0)  goto ERROR_OUT;
    if(FD_ISSET(sock, &rset) || FD_ISSET(sock, &wset))
    {
        socklen_t server_addr_len;
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr_len=sizeof(server_addr);
        if(getpeername(sock, (struct sockaddr *)&server_addr,
                    &server_addr_len) == SOCKET_ERROR)
            goto ERROR_OUT;
    }
    else
        goto ERROR_OUT;

DONE:
    if(fcntl(sock, F_SETFL, flags) == -1)
        goto ERROR_OUT;
    return(sock);

ERROR_OUT:
    sock_close(sock);
    return(INVALID_SOCKET);
}/*}}}*/

SOCKET sock_accept(SOCKET listen_sock, int timeout)
{/*{{{*/
    int timeout_ms=timeout*1000;
    int res;
    SOCKET new_sock;
    socklen_t client_len;
    struct sockaddr_in client_addr;

    if((res=_wait(_ACCEPT, listen_sock, &timeout_ms)) != 0)
    {
        if(res == RET_ERR_TIMEOUT)
            return(res);
        else
            return(INVALID_SOCKET);
    }

AGAIN:
    client_len=sizeof(client_addr);
    if((new_sock=accept(listen_sock, (struct sockaddr *)&client_addr,
                    &client_len)) == INVALID_SOCKET)
    {
        int errnum=get_last_error(NULL);
        if(errnum == ECONNABORTED || errnum == EINTR)
        {
            goto AGAIN;
        }
        return(INVALID_SOCKET);
    }

    return(new_sock);
}/*}}}*/

void sock_close(SOCKET sock)
{/*{{{*/
    if(sock < 0)  return;
    close(sock);
}/*}}}*/

int sock_read(SOCKET sock, unsigned char *msg, int *msg_size,
    int timeout, unsigned int flag)
{/*{{{*/
    int timeout_ms=timeout*1000;
    int nread, nleft;
    int msg_out_size;
    unsigned char *ptr;
    int res;

    /* 不带消息长度 */
    if(!_MSG_IS_WRAP)
    {
        if((res=_wait(_RECV, sock, &timeout_ms)) != 0)
        {
            if(res == RET_ERR_TIMEOUT)
            {
                *msg_size=0;
                msg[0]=ZERO;
                return(res);
            }
            return(-3);
        }
        if((nread=recv(sock, msg, SOCK_MSG_SIZE, 0)) == SOCKET_ERROR)
        {
            return(-2);
        }
        // 套接口接收FIN
        if(nread == 0)
        {
            return(RET_ERR_ABORT);
        }
        *msg_size=nread;
        msg[nread]=ZERO;
        return(0);
    }

    /* 带消息长度 */
    // 消息长度
    if((res=_wait(_RECV, sock, &timeout_ms)) != 0)
    {
        if(res == RET_ERR_TIMEOUT)
        {
            *msg_size=0;
            msg[0]=ZERO;
            return(res);
        }
        return(-3);
    }
    if((nread=recv(sock, msg, MSG_SIZE_SIZE, 0)) == SOCKET_ERROR)
    {
        return(-2);
    }
    if(nread < MSG_SIZE_SIZE)   // 套接口接收FIN
    {
        return(RET_ERR_ABORT);
    }
    msg[MSG_SIZE_SIZE]=ZERO;
    msg_out_size=strtol((const char *)msg, NULL, 16);
    if(msg_out_size <= 0 || msg_out_size > SOCK_MSG_SIZE)
    {
        return(-1);
    }

    // 消息主体
    nleft=msg_out_size;
    ptr=msg;
    while(nleft > 0)
    {
AGAIN:
        if((nread=recv(sock, ptr, nleft, 0)) == SOCKET_ERROR)
        {
            int errnum=get_last_error(NULL);
            if(errnum == EINTR)
                goto AGAIN;
            *msg_size=msg_out_size-nleft;
            msg[msg_out_size-nleft]=ZERO;
            return(-2);
        }
        if(nread == 0)  // 套接口接收FIN
        {
            return(RET_ERR_ABORT);
        }
        nleft-=nread;
        ptr+=nread;
        if(nleft > 0)
        {
            if((res=_wait(_RECV, sock, &timeout_ms)) != 0)
            {
                if(res == RET_ERR_TIMEOUT)
                {
                    *msg_size=msg_out_size-nleft;
                    msg[msg_out_size-nleft]=ZERO;
                    return(res);
                }
                return(-3);
            }
        }
        //printf("nread:%d timeout_ms:%d\n", nread, timeout_ms);
    }

    *msg_size=msg_out_size;
    msg[msg_out_size]=ZERO;
    return(0);
}/*}}}*/

int sock_write(SOCKET sock, unsigned char *msg, int *msg_size,
    int timeout, unsigned int flag)
{/*{{{*/
    int msg_in_size=*msg_size;
    int timeout_ms=timeout*1000;
    int res;
    unsigned char *msg_out;
    int nwrite, nleft;
    unsigned char *ptr;

    if(msg_in_size <= 0 || msg_in_size > SOCK_MSG_SIZE)
    {
        return(-1);
    }

    if((res=_wait(_SEND, sock, &timeout_ms)) != 0)
    {
        if(res == RET_ERR_TIMEOUT)
        {
            *msg_size=0;
            return(res);
        }
        return(-3);
    }

    if(_MSG_IS_WRAP)    // 带消息长度
    {
        ALLOC_MULTI_RETERR(msg_out, unsigned char, msg_in_size+32, -1);
        wrap_size(msg, &msg_in_size, msg_out);
    }
    else     // 不带消息长度
    {
        msg_out=msg;
    }

    ptr=msg_out;
    nleft=msg_in_size;
    while(nleft>0)
    {
AGAIN:
        if((nwrite=send(sock, ptr, nleft, 0))==SOCKET_ERROR)
        {
            int errnum=get_last_error(NULL);
            if(errnum == EINTR)
                goto AGAIN;
            *msg_size=msg_in_size-nleft;
            if(_MSG_IS_WRAP)  FREE(msg_out);
            return(-2);
        }
        nleft-=nwrite;
        ptr+=nwrite;
        if(nleft > 0)
        {
            if((res=_wait(_SEND, sock, &timeout_ms)) != 0)
            {
                if(res == RET_ERR_TIMEOUT)
                {
                    if(_MSG_IS_WRAP)  FREE(msg_out);
                    *msg_size=msg_in_size-nleft;
                    return(res);
                }
                return(-3);
            }
        }
    }

    if(_MSG_IS_WRAP)  FREE(msg_out);
    *msg_size=msg_in_size;
    return(0); 
}/*}}}*/

/*========*/
/* UNIX域 */
/*========*/
SOCKET sock_local_listen(char *sun_path)
{/*{{{*/
    SOCKET listen_sock;
    struct sockaddr_un server_addr;
    mode_t m;

    if((listen_sock=socket(AF_UNIX, SOCK_STREAM, 0))==INVALID_SOCKET)
        return(INVALID_SOCKET);

    if(!sun_path)  return(INVALID_SOCKET);
    if(strlen(sun_path) > sizeof(server_addr.sun_path)-1)
        return(INVALID_SOCKET);

    bzero(&server_addr, sizeof(server_addr));
    server_addr.sun_family=AF_UNIX;
    strcpy(server_addr.sun_path, "/tmp/");
    strcat(server_addr.sun_path, sun_path);
    unlink(server_addr.sun_path);
    m=umask((mode_t)0000);
    if(bind(listen_sock, (const struct sockaddr *)&server_addr,
           sizeof(server_addr)) == SOCKET_ERROR)
    {   umask(m); return(INVALID_SOCKET);   }
    umask(m);
    if(listen(listen_sock, 1024) == SOCKET_ERROR)
        return(INVALID_SOCKET);

    return(listen_sock);
}/*}}}*/

SOCKET sock_local_connect(char *sun_path)
{/*{{{*/
    SOCKET conn_sock;
    struct sockaddr_un client_addr;

    if((conn_sock=socket(AF_UNIX, SOCK_STREAM, 0))==INVALID_SOCKET)
        return(INVALID_SOCKET);
    if(!sun_path)  return(INVALID_SOCKET);
    if(strlen(sun_path)>sizeof(client_addr.sun_path)-1)
        return(INVALID_SOCKET);

    memset(&client_addr, 0, sizeof(client_addr));
    client_addr.sun_family=AF_UNIX;
    strcpy(client_addr.sun_path, "/tmp/");
    strcat(client_addr.sun_path, sun_path);
    if(connect(conn_sock, (const struct sockaddr *)&client_addr,
           sizeof(client_addr))==INVALID_SOCKET)
    {
        return(INVALID_SOCKET);
    }

    return(conn_sock);
}/*}}}*/

int sock_local_pair(SOCKET sockpair[2])
{/*{{{*/
    if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair) == SOCKET_ERROR)
    {
        sockpair[0]=sockpair[1]=INVALID_SOCKET;
        return(-1);
    }

    return(0);
}/*}}}*/
